/*
 * PsMediaReader.cpp
 *
 *  Created on: 2018年9月1日
 *      Author: zhengboyuan
 */

#include "PsMediaReader.h"
#include "CLog.h"
#include "H264PropParser.h"
#include "Path.h"


namespace av
{

PsMediaReader::PsMediaReader():
		m_index(-1),
		m_file(),
		m_pts(),
		m_startPts()
{
}

PsMediaReader::~PsMediaReader()
{
	close();
}

PsMediaReader::PsMediaReader(MRecord* records, int size):
		m_index(-1),
		m_file(),
		m_pts()
{
	setRecords(records, size);
}

void PsMediaReader::setRecords(MRecord* records, int size)
{
	if (!records || size <= 0)
	{
		return;
	}

	m_records.assign(records, records + size);
}

int PsMediaReader::open(const std::string& url, const std::string& params)
{
	if (m_records.size() <= 0)
	{
		return ENOENT;
	}

	m_file = openNextFile();
	if (!m_file)
	{
		return ENOENT;
	}

	while (m_file)
	{
		if (getMediaFormat(m_file, m_format))
		{
			break;
		}
		else
		{
			fclose(m_file);
			m_file = NULL;

			m_file = openNextFile();
		}
	}

	if (!m_format.isValid())
	{
		return EBADF;
	}

	int duration = countDuration(m_records, m_index);
	m_format.m_duration = duration * 1000;
	m_format.m_clockRate = AV_TIME_BASE;
	m_format.m_audioRate = AV_TIME_BASE;


	fseek(m_file, 0, SEEK_SET);

	m_psParser.clear();

	return 0;
}

void PsMediaReader::close()
{
	closeFile();
}

bool PsMediaReader::isOpen()
{
	return m_format.isValid();
}

bool PsMediaReader::getFormat(MediaFormat& fmt)
{
	fmt = m_format;
	return true;
}

int PsMediaReader::getDuration()
{
	return m_format.m_duration;
}

int PsMediaReader::play()
{
	return 0;
}

int PsMediaReader::pause()
{
	return 0;
}

void PsMediaReader::stop()
{
	// pass
}

int PsMediaReader::getState()
{
	return av::STATE_PLAYING;
}

bool PsMediaReader::seekable()
{
	return true;
}

int PsMediaReader::seek(int64_t offset)
{
	int64_t subOffset = 0;
	int index = findRecord(offset, subOffset);

	seekRecord(index, subOffset);

	return 0;
}

int64_t PsMediaReader::getTime()
{
	return m_pts;
}

int PsMediaReader::setScale(float scale)
{
	return ENOSYS;
}

float PsMediaReader::getScale()
{
	return 1.0f;
}

int PsMediaReader::read(AVPacket& pkt)
{
	int rc = EIO;
	while (m_file)
	{
		uint8_t buffer[1024] = {0};
		size_t size = fread(buffer, 1, 1024, m_file);
		if (size == 0)
		{
			closeFile();

			m_file = openNextFile();
			if (!m_file)
			{
				break;
			}

			CLog::info("switch to file: %s\n", getCurrentFilePath());

			continue;
		}

		PsParser::Packet packet;
		if (!m_psParser.parse(buffer, size, packet))
		{
			continue;
		}

		av_new_packet(&pkt, packet.length);
		memcpy(pkt.data, packet.data, packet.length);
		pkt.size = packet.length;
		pkt.pts = packet.ts * 1000 / 90;
		pkt.stream_index = packet.type;
		pkt.flags = packet.flags;

		m_pts = pkt.pts;

		rc = 0;
		break;
	}
	return rc;
}

void PsMediaReader::interrupt()
{
	// pass
}

bool PsMediaReader::isLive()
{
	return false;
}

FILE* PsMediaReader::openNextFile()
{
	FILE* file = NULL;
	size_t idx = m_records.size();
	size_t nextIndex = m_index + 1;
	for (size_t i = nextIndex; i < m_records.size(); i ++)
	{
		const MRecord& record = m_records[i];

		file = fopen(record.filepath, "rb");
		if (file)
		{
			idx = i;
			break;
		}
		else
		{
			CLog::warning("failed to open file: %s\n", record.filepath);
		}
	}

	m_index = idx;
	return file;
}

bool PsMediaReader::getMediaFormat(FILE* file, MediaFormat& fmt)
{
	av::PsParser& parser = m_psParser;

	bool found = false;
	while (!feof(file))
	{
		uint8_t buffer[1024] = {0};
		size_t size = fread(buffer, 1, 1024, file);
		if (size == 0)
		{
			break;
		}

		PsParser::Packet pkt;
		if (!parser.parse(buffer, size, pkt))
		{
			continue;
		}

		if (pkt.type == av::MEDIA_TYPE_VIDEO)
		{
			if (m_startPts <= 0)
			{
				m_startPts = pkt.ts;
			}
		}

		const av::MediaFormat& format = parser.getFormat();
		if (format.m_codec != av::MEDIA_CODEC_NONE)
		{
			fmt = parser.getFormat();
			found = true;
			break;
		}
	}
	return found;
}

void PsMediaReader::closeFile()
{
	if (m_file)
	{
		fclose(m_file);
		m_file = NULL;
	}
}

int PsMediaReader::countDuration(const MRecordArray& records, size_t offset)
{
	int duration = 0;

	if (offset > records.size())
	{
		offset = 0;
	}

	for (size_t i = offset; i < records.size(); i ++)
	{
		const MRecord& record = records[i];
		duration += record.duration;
	}
	return duration;
}

MRecord PsMediaReader::getCurrentRecord()
{
	if (m_index >= 0 && m_index < (int)m_records.size())
	{
		return m_records[m_index];
	}
	return MRecord();
}

const char* PsMediaReader::getCurrentFilePath()
{
	if (m_index >= 0 && m_index < (int)m_records.size())
	{
		return m_records[m_index].filepath;
	}
	return "";
}

int PsMediaReader::findRecord(int64_t offset, int64_t& subPffset)
{
	if (offset <= 0)
	{
		return 0;
	}

	int index = 0;
	time_t startTime = offset / 1000 + m_records[0].startTime;

	for (size_t i = 0; i < m_records.size(); i ++)
	{
		if (startTime >= m_records[i].startTime)
		{
			index = i;
		}
		else
		{
			break;
		}
	}

	subPffset = (startTime - m_records[index].startTime) * 1000 + offset % 1000;

	return index;
}

bool PsMediaReader::seekRecord(int index, int64_t offset)
{
	closeFile();

	int curIndex = index;
	FILE* file = openNextFile(curIndex);

	if (curIndex != index)
	{
		/// 起始文件破损
		m_index = index;
		m_file = file;
	}
	else
	{
		seekRecord(m_records[curIndex], file, offset);

		m_index = index;
		m_file = file;
	}

	clearCache();

	return true;
}

static int64_t SEEK_WINDOW_SIZE = 1024 * 128;

bool PsMediaReader::seekRecord(const MRecord& record, FILE* file, int64_t offset)
{
	int64_t fileSize = comn::Path::getSize(record.filepath);

	if (record.duration > 0)
	{
		int64_t pos = offset * fileSize / (record.duration * 1000);
		fseek(file, pos, SEEK_SET);

		return true;
	}

	if (fileSize <= SEEK_WINDOW_SIZE)
	{
		return true;
	}

	PsFileStream psFileStream;
	if (!psFileStream.open(record.filepath))
	{
		return false;
	}

	PsPacket pktFront = PsPacket();
	if (!readMediaPacket(psFileStream, pktFront))
	{
		return false;
	}

	int64_t ptsStart = 0;
	PsFileStream::getTimeStamp(pktFront, ptsStart);

	int64_t ptsSeek = ptsStart + offset * 90;

	PsPacket pktBack = PsPacket();
	if (!findBackPacket(psFileStream, fileSize, pktBack))
	{
		return false;
	}

	int64_t ptsStop = ptsStart;
	PsFileStream::getTimeStamp(pktBack, ptsStop);

	int64_t duration = (ptsStop - ptsStart) / 90;
	if (duration > 0)
	{
		int64_t pos = offset * fileSize / duration;
		fseek(file, pos, SEEK_SET);
	}

	return true;
}

FILE* PsMediaReader::openNextFile(int& fromIndex)
{
	FILE* file = NULL;
	size_t idx = m_records.size();
	size_t nextIndex = fromIndex;
	for (size_t i = nextIndex; i < m_records.size(); i ++)
	{
		const MRecord& record = m_records[i];

		file = fopen(record.filepath, "rb");
		if (file)
		{
			idx = i;
			break;
		}
		else
		{
			CLog::warning("failed to open file: %s\n", record.filepath);
		}
	}

	fromIndex = idx;
	return file;
}

void PsMediaReader::clearCache()
{
	m_psParser.clear();
	m_pts = 0;
}

bool PsMediaReader::readMediaPacket(PsFileStream& psFileStream, PsPacket& packet)
{
	bool found = false;
	PsPacket pkt = PsPacket();
	while (psFileStream.read(pkt))
	{
		if (pkt.type == kPsVideoPacket)
		{
			packet = pkt;
			found = true;
			break;
		}
	}
	return found;
}

bool PsMediaReader::findBackPacket(PsFileStream& psFileStream, int64_t fileSize, PsPacket& packet)
{
	bool found = false;
	int tailSize = SEEK_WINDOW_SIZE;
	for (int i = 0; i < 4; i ++)
	{
		if (fileSize <= tailSize)
		{
			break;
		}

		psFileStream.seekg(fileSize - tailSize);
		found = readMediaPacket(psFileStream, packet);
		if (found)
		{
			break;
		}

		tailSize *= 2;
	}
	return found;
}


} /* namespace av */
